#include <QtCore/QTimer>
#include <QtCore/QtEndian>
#include <QtCore/QDateTime>
#include <QtSql/QSqlRecord>
#include "global.h"
#include "kernel/kernel.h"
#include "kernel/kernel_p.h"

namespace smsrouter {

static Kernel *kernel_instance = 0;

KernelPrivate::KernelPrivate() :
  QObject(0),
  settings(new KernelSettings(0,
                              QHostAddress(),
                              0,
                              QHostAddress(),
                              0,
                              10,
                              10,
                              10,
                              30,
                              180,
                              60,
                              100,
                              100,
                              QString(),
                              0,
                              QHostAddress(),
                              0)),
  log_level(smslog::WARNING),
  db_settings_port(-1),
  num_connect_settings_db(-1),
  db_directions_port(-1),
  num_connect_directions_db(-1),
  db_messages_port(-1),
  num_connect_messages_db(-1),
  session_manager(0),
  mailer(0),
  settings_db(0),
  directions_db(0),
  messages_db(0),
  billing_client(0),
  rpc_server(0),
  log_decoder(0),
  thread(new QThread(this)),
  state(STOPED) {
  moveToThread(thread);
  connect(thread, SIGNAL(started()), this, SLOT(threadStarted()));
  connect(this, SIGNAL(sigStop()), this, SLOT(stop()));
}

KernelPrivate::~KernelPrivate() {
  if(session_manager) {
    delete session_manager;
    session_manager = 0;
  }
  if(mailer) {
    delete mailer;
    mailer = 0;
  }
  if(settings_db) {
    delete settings_db;
    settings_db = 0;
  }
  if(directions_db) {
    delete directions_db;
    directions_db = 0;
  }
  if(messages_db) {
    delete messages_db;
    messages_db = 0;
  }
  if(rpc_server) {
    delete rpc_server;
    rpc_server = 0;
  }
  if(billing_client) {
    delete billing_client;
    billing_client = 0;
  }
  if(log_decoder) {
    delete log_decoder;
    log_decoder = 0;
  }

  kernel_instance = 0;
}

void KernelPrivate::threadStarted() {
  qsrand(QTime::currentTime().msec());

  settings_db = new dbmanager::DatabaseManager(
    db_settings_host,
    db_settings_port,
    db_settings_name,
    db_settings_uname,
    db_settings_pass,
    num_connect_settings_db,
    KERNEL_KEY);

  directions_db = new dbmanager::DatabaseManager(
    db_directions_host,
    db_directions_port,
    db_directions_name,
    db_directions_uname,
    db_directions_pass,
    num_connect_directions_db,
    DIRECTIONS_KEY);

  messages_db = new dbmanager::DatabaseManager(
    db_messages_host,
    db_messages_port,
    db_messages_name,
    db_messages_uname,
    db_messages_pass,
    num_connect_messages_db,
    MESSAGES_KEY);

  session_manager = new SessionManager;

  mailer = new Mailer;

  billing_client = new binaryrpc::Client;

  rpc_server = new binaryrpc::Server;

  log_decoder = new LogDecoder;

  connect(billing_client, SIGNAL(onOpen()),
          this, SLOT(connectBillingClient()));
  connect(billing_client, SIGNAL(onClose()),
          this, SLOT(closeBillingClient()));
  connect(billing_client, SIGNAL(onError(const QString &)),
          this, SLOT(errorBillingClient(const QString &)));

  connect(rpc_server, SIGNAL(newSession(binaryrpc::Session *)),
          this, SLOT(newRpcServerSession(binaryrpc::Session *)));

  connect(mailer,
          SIGNAL(send(SrcSubmitSm *)),
          this,
          SLOT(send(SrcSubmitSm *)));

  connect(mailer,
          SIGNAL(send(DestSubmitSm *)),
          this,
          SLOT(firewall(DestSubmitSm *)));

  connect(mailer,
          SIGNAL(send(SrcDeliverSm *)),
          session_manager,
          SLOT(send(SrcDeliverSm *)));

  connect(mailer,
          SIGNAL(stateChanged(State)),
          this,
          SLOT(mailerStateChanged(State)));

  connect(session_manager,
          SIGNAL(receive(SrcSubmitSm *)),
          this,
          SLOT(firewall(SrcSubmitSm *)));

  connect(session_manager,
          SIGNAL(responced(SrcSubmitSm *)),
          mailer,
          SLOT(add(SrcSubmitSm *)));

  connect(session_manager,
          SIGNAL(responce(DestSubmitSm *)),
          mailer,
          SLOT(add(DestSubmitSm *)));

  connect(session_manager,
          SIGNAL(responce(SrcDeliverSm *)),
          this,
          SLOT(dmResponced(SrcDeliverSm *)));

  connect(session_manager,
          SIGNAL(responced(DestDeliverSm *)),
          this,
          SLOT(dmResponced(DestDeliverSm *)));

  connect(session_manager,
          SIGNAL(stateChanged(State)),
          this,
          SLOT(sessionManagerStateChanged(State)));

  setState(STARTING);
}

void KernelPrivate::stop() {
  setState(STOPING);
}

void KernelPrivate::firewall(SrcSubmitSm *message) {
  route(message);

  transform(message);

  filter(message);

  if(message->error_code == smpp::parameters::command_status::ESME_ROK) {
    if(requestBilling(message)) {
      return;
    }
  }

  session_manager->responce(message);
}

void KernelPrivate::firewall(DestSubmitSm *message) {
  filter(message);

  if(message->error_code == smpp::parameters::command_status::ESME_ROK) {
    if(!requestBilling(message)) {
      session_manager->send(message);
    }
  } else {
    mailer->add(message);
  }
}

void KernelPrivate::newRpcServerSession(binaryrpc::Session *session) {
  connect(session, SIGNAL(onOpen()), this, SLOT(openRpcServerSession()));
  connect(session, SIGNAL(onClose()), this, SLOT(closeRpcServerSession()));

  if(state == STARTED) {
    connect(session, SIGNAL(onError(const QString &)),
            this, SLOT(errorRpcServerSession(const QString &)));
    connect(session, SIGNAL(newMessage(binaryrpc::ServerMessage *)),
            this, SLOT(newMessageRpcServerSession(binaryrpc::ServerMessage *)));
  } else {
    session->close();
  }
}

void KernelPrivate::openRpcServerSession() {
  binaryrpc::Session *session = qobject_cast<binaryrpc::Session *>(sender());

  if(!session) {
    return;
  }

  if(log_level >= smslog::INFO) {
    QMultiHash<int, QVariant> params;
    params.insert(logparam::PEER_IP_P, session->peerAddress().toString());
    params.insert(logparam::PEER_PORT_P, session->peerPort());
    params.insert(logparam::LOCAL_IP_P, session->localAddress().toString());
    params.insert(logparam::LOCAL_PORT_P, session->localPort());

    LogEncoder::instance()->encode(smslog::INFO,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::K_RPC_SRV_OPEN,
                                   params);
  }

  rpc_server_sessions.push_back(session);
}

void KernelPrivate::closeRpcServerSession() {
  binaryrpc::Session *session(qobject_cast<binaryrpc::Session *>(sender()));

  if(!session) {
    return;
  }

  if(log_level >= smslog::INFO) {
    QMultiHash<int, QVariant> params;
    params.insert(logparam::PEER_IP_P, session->peerAddress().toString());
    params.insert(logparam::PEER_PORT_P, session->peerPort());
    params.insert(logparam::LOCAL_IP_P, session->localAddress().toString());
    params.insert(logparam::LOCAL_PORT_P, session->localPort());

    LogEncoder::instance()->encode(smslog::INFO,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::K_RPC_SRV_CLOSE,
                                   params);
  }

  rpc_server_sessions.removeAll(session);

  delete session;
}

void KernelPrivate::errorRpcServerSession(const QString &error_string) {
  binaryrpc::Session *session(qobject_cast<binaryrpc::Session *>(sender()));

  if(!session) {
    return;
  }

  if(log_level >= smslog::WARNING) {
    QMultiHash<int, QVariant> params;
    params.insert(logparam::ERROR_MSG_P, error_string);
    params.insert(logparam::PEER_IP_P, session->peerAddress().toString());
    params.insert(logparam::PEER_PORT_P, session->peerPort());
    params.insert(logparam::LOCAL_IP_P, session->localAddress().toString());
    params.insert(logparam::LOCAL_PORT_P, session->localPort());

    LogEncoder::instance()->encode(smslog::WARNING,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::K_RPC_SRV_ERROR,
                                   params);
  }

  rpc_server_sessions.removeAll(session);

  delete session;
}

void KernelPrivate::newMessageRpcServerSession(
  binaryrpc::ServerMessage *message) {
  binaryrpc::Session *session(qobject_cast<binaryrpc::Session *>(sender()));

  if(!session) {
    return;
  }

  QString method_name(message->name());

  if(method_name == "getLogMessages") {
    log_decoder->decode(message);
  } else if(method_name == "getLogLevel") {
    message->done(QVariant((char)getLogLevel()));
  } else if(method_name == "setLogLevel") {
    QVariantList parameters(message->parameters());

    bool success(parameters.size() == 1);

    if(!success) {
      message->fault(BRPC_ERR_CODE_NOT_FOUND, BRPC_ERR_CODE_NOT_FOUND_D);
      message->deleteLater();
      return;
    }

    char log_level_char(parameters[0].toChar().toLatin1());

    if(log_level_char > 0 && log_level_char < 6) {
      lock.lockForWrite();
      log_level = (smslog::LogLevel)log_level_char;
      lock.unlock();
      message->done(true);
    } else {
      message->done(false);
    }
  } else {
    message->fault(BRPC_ERR_CODE_NOT_FOUND, BRPC_ERR_CODE_NOT_FOUND_D);
    message->deleteLater();
  }
}

void KernelPrivate::reconnectBillingClient() {
  billing_client->open(billing_client->peerAddress(),
                       billing_client->peerPort());
}

void KernelPrivate::connectBillingClient() {
  if(Kernel::instance()->getLogLevel() >= smslog::INFO) {
    QMultiHash<int, QVariant> params;
    params.insert(logparam::LOCAL_IP_P,
                  billing_client->localAddress().toString());
    params.insert(logparam::LOCAL_PORT_P, billing_client->localPort());
    params.insert(logparam::PEER_IP_P,
                  billing_client->peerAddress().toString());
    params.insert(logparam::PEER_PORT_P, billing_client->peerPort());

    LogEncoder::instance()->encode(smslog::INFO,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::K_BILLING_CL_OPEN,
                                   params);
  }
}

void KernelPrivate::closeBillingClient() {
  if(Kernel::instance()->getLogLevel() >= smslog::INFO) {
    QMultiHash<int, QVariant> params;
    params.insert(logparam::PEER_IP_P,
                  billing_client->peerAddress().toString());
    params.insert(logparam::PEER_PORT_P, billing_client->peerPort());

    LogEncoder::instance()->encode(smslog::INFO,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::K_BILLING_CL_CLOSE,
                                   params);
  }
}

void KernelPrivate::errorBillingClient(const QString &error_message) {
  if(Kernel::instance()->getLogLevel() >= smslog::WARNING) {
    QMultiHash<int, QVariant> params;
    params.insert(logparam::PEER_IP_P,
                  billing_client->peerAddress().toString());
    params.insert(logparam::PEER_PORT_P, billing_client->peerPort());
    params.insert(logparam::ERROR_MSG_P, error_message);

    LogEncoder::instance()->encode(smslog::WARNING,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::K_BILLING_CL_ERROR,
                                   params);
  }

  if(state != STOPING || state != STOPED) {
    QTimer::singleShot(5000, this, SLOT(reconnectBillingClient()));
  }
}

void KernelPrivate::doneSrcSubmitBilling(binaryrpc::ClientMessage *billing_msg,
                                         const QVariant &return_value) {
  SrcSubmitSm *message(billing_src_submit.value(billing_msg, 0));

  if(message) {
    bool success_send_sms(return_value.toBool());

    if(success_send_sms == false) {
      message->error_code = smpp::parameters::command_status::ESME_RX_R_APPN;
    }

    if(Kernel::instance()->getLogLevel() == smslog::DEBUG) {
      QMultiHash<int, QVariant> params;

      binaryrpc::BinaryEncoder encoder;

      encoder.resize(binaryrpc::getSize(return_value));

      encoder << return_value;

      params.insert(logparam::RPC_METHOD_NAME_P, billing_msg->name());
      params.insert(logparam::RPC_RET_VAL_P, encoder.getBuff());
      params.insert(logparam::PEER_IP_P,
                    billing_client->peerAddress().toString());
      params.insert(logparam::PEER_PORT_P, billing_client->peerPort());

      LogEncoder::instance()->encode(smslog::DEBUG,
                                     logclass::KERNEL,
                                     logclass::KERNEL,
                                     logmsgcodes::K_BILLING_CL_DONE,
                                     params);
    }

    billing_src_submit.remove(billing_msg);

    session_manager->responce(message);
  }

  delete billing_msg;
  checkState();
}

void KernelPrivate::faultSrcSubmitBilling(binaryrpc::ClientMessage *billing_msg,
                                          int fault_code,
                                          const QString &fault_string) {
  SrcSubmitSm *message(billing_src_submit.value(billing_msg, 0));

  if(message) {
    message->error_code = smpp::parameters::command_status::ESME_RX_T_APPN;

    if(Kernel::instance()->getLogLevel() >= smslog::WARNING) {
      QMultiHash<int, QVariant> params;
      params.insert(logparam::RPC_METHOD_NAME_P, billing_msg->name());
      params.insert(logparam::ERROR_CODE_P, fault_code);
      params.insert(logparam::ERROR_MSG_P, fault_string);
      params.insert(logparam::PEER_IP_P,
                    billing_client->peerAddress().toString());
      params.insert(logparam::PEER_PORT_P, billing_client->peerPort());

      LogEncoder::instance()->encode(smslog::WARNING,
                                     logclass::KERNEL,
                                     logclass::KERNEL,
                                     logmsgcodes::K_BILLING_CL_FAULT,
                                     params);
    }

    billing_src_submit.remove(billing_msg);

    session_manager->responce(message);
  }

  delete billing_msg;
  checkState();
}

void KernelPrivate::doneSrcDeliverBilling(binaryrpc::ClientMessage *billing_msg,
                                          const QVariant &return_value) {
  SrcDeliverSm *message(billing_src_deliver.value(billing_msg, 0));

  if(message) {
    bool success_send_sms(return_value.toBool());

    if(Kernel::instance()->getLogLevel() == smslog::DEBUG) {
      QMultiHash<int, QVariant> params;

      binaryrpc::BinaryEncoder encoder;

      encoder.resize(binaryrpc::getSize(return_value));

      encoder << return_value;

      params.insert(logparam::RPC_METHOD_NAME_P, billing_msg->name());
      params.insert(logparam::RPC_RET_VAL_P, encoder.getBuff());
      params.insert(logparam::PEER_IP_P,
                    billing_client->peerAddress().toString());
      params.insert(logparam::PEER_PORT_P, billing_client->peerPort());

      LogEncoder::instance()->encode(smslog::DEBUG,
                                     logclass::KERNEL,
                                     logclass::KERNEL,
                                     logmsgcodes::K_BILLING_CL_DONE,
                                     params);
    }

    if(success_send_sms == false) {
      message->error_code = smpp::parameters::command_status::ESME_RX_R_APPN;
    }

    billing_src_deliver.remove(billing_msg);

    mailer->add(message);
  }

  delete billing_msg;
  checkState();
}

void KernelPrivate::faultSrcDeliverBilling(
  binaryrpc::ClientMessage *billing_msg,
  int fault_code,
  const QString &fault_string) {
  SrcDeliverSm *message(billing_src_deliver.value(billing_msg, 0));

  if(message) {
    message->error_code = smpp::parameters::command_status::ESME_RX_T_APPN;

    if(Kernel::instance()->getLogLevel() >= smslog::WARNING) {
      QMultiHash<int, QVariant> params;
      params.insert(logparam::RPC_METHOD_NAME_P, billing_msg->name());
      params.insert(logparam::ERROR_CODE_P, fault_code);
      params.insert(logparam::ERROR_MSG_P, fault_string);
      params.insert(logparam::PEER_IP_P,
                    billing_client->peerAddress().toString());
      params.insert(logparam::PEER_PORT_P, billing_client->peerPort());

      LogEncoder::instance()->encode(smslog::WARNING,
                                     logclass::KERNEL,
                                     logclass::KERNEL,
                                     logmsgcodes::K_BILLING_CL_FAULT,
                                     params);
    }

    billing_src_deliver.remove(billing_msg);

    mailer->add(message);
  }

  delete billing_msg;
  checkState();
}

void KernelPrivate::doneDestSubmitBilling(binaryrpc::ClientMessage *billing_msg,
                                          const QVariant &return_value) {
  DestSubmitSm *message(billing_dest_submit.value(billing_msg, 0));

  if(message) {
    bool success_send_sms(return_value.toBool());

    if(success_send_sms) {
      session_manager->send(message);
    } else {
      message->error_code = smpp::parameters::command_status::ESME_RX_R_APPN;
      mailer->add(message);
    }

    if(Kernel::instance()->getLogLevel() == smslog::DEBUG) {
      QMultiHash<int, QVariant> params;

      binaryrpc::BinaryEncoder encoder;

      encoder.resize(binaryrpc::getSize(return_value));

      encoder << return_value;

      params.insert(logparam::RPC_METHOD_NAME_P, billing_msg->name());
      params.insert(logparam::RPC_RET_VAL_P, encoder.getBuff());
      params.insert(logparam::PEER_IP_P,
                    billing_client->peerAddress().toString());
      params.insert(logparam::PEER_PORT_P, billing_client->peerPort());

      LogEncoder::instance()->encode(smslog::DEBUG,
                                     logclass::KERNEL,
                                     logclass::KERNEL,
                                     logmsgcodes::K_BILLING_CL_DONE,
                                     params);
    }

    billing_dest_submit.remove(billing_msg);
  }

  delete billing_msg;
  checkState();
}

void KernelPrivate::faultDestSubmitBilling(
  binaryrpc::ClientMessage *billing_msg,
  int fault_code,
  const QString &fault_string) {
  DestSubmitSm *message(billing_dest_submit.value(billing_msg, 0));

  if(message) {
    message->error_code = smpp::parameters::command_status::ESME_RX_T_APPN;

    if(Kernel::instance()->getLogLevel() >= smslog::WARNING) {
      QMultiHash<int, QVariant> params;
      params.insert(logparam::RPC_METHOD_NAME_P, billing_msg->name());
      params.insert(logparam::ERROR_CODE_P, fault_code);
      params.insert(logparam::ERROR_MSG_P, fault_string);
      params.insert(logparam::PEER_IP_P,
                    billing_client->peerAddress().toString());
      params.insert(logparam::PEER_PORT_P, billing_client->peerPort());

      LogEncoder::instance()->encode(smslog::WARNING,
                                     logclass::KERNEL,
                                     logclass::KERNEL,
                                     logmsgcodes::K_BILLING_CL_FAULT,
                                     params);
    }

    billing_src_submit.remove(billing_msg);

    mailer->add(message);
  }

  delete billing_msg;
  checkState();
}

void KernelPrivate::doneDestDeliverBilling(binaryrpc::ClientMessage *billing_msg,
                                          const QVariant &return_value) {
  DestDeliverSm *message(billing_dest_deliver.value(billing_msg, 0));

  if(message) {
    bool success_send_sms(return_value.toBool());

    if(Kernel::instance()->getLogLevel() == smslog::DEBUG) {
      QMultiHash<int, QVariant> params;

      binaryrpc::BinaryEncoder encoder;

      encoder.resize(binaryrpc::getSize(return_value));

      encoder << return_value;

      params.insert(logparam::RPC_METHOD_NAME_P, billing_msg->name());
      params.insert(logparam::RPC_RET_VAL_P, encoder.getBuff());
      params.insert(logparam::PEER_IP_P,
                    billing_client->peerAddress().toString());
      params.insert(logparam::PEER_PORT_P, billing_client->peerPort());

      LogEncoder::instance()->encode(smslog::DEBUG,
                                     logclass::KERNEL,
                                     logclass::KERNEL,
                                     logmsgcodes::K_BILLING_CL_DONE,
                                     params);
    }

    if(success_send_sms == false) {
      message->error_code = smpp::parameters::command_status::ESME_RX_R_APPN;
    }

    billing_src_deliver.remove(billing_msg);

    mailer->add(message);
  }

  delete billing_msg;
  checkState();
}

void KernelPrivate::faultDestDeliverBilling(
  binaryrpc::ClientMessage *billing_msg,
  int fault_code,
  const QString &fault_string) {
  DestDeliverSm *message(billing_dest_deliver.value(billing_msg, 0));

  if(message) {
    message->error_code = smpp::parameters::command_status::ESME_RX_T_APPN;

    if(Kernel::instance()->getLogLevel() >= smslog::WARNING) {
      QMultiHash<int, QVariant> params;
      params.insert(logparam::RPC_METHOD_NAME_P, billing_msg->name());
      params.insert(logparam::ERROR_CODE_P, fault_code);
      params.insert(logparam::ERROR_MSG_P, fault_string);
      params.insert(logparam::PEER_IP_P,
                    billing_client->peerAddress().toString());
      params.insert(logparam::PEER_PORT_P, billing_client->peerPort());

      LogEncoder::instance()->encode(smslog::WARNING,
                                     logclass::KERNEL,
                                     logclass::KERNEL,
                                     logmsgcodes::K_BILLING_CL_FAULT,
                                     params);
    }

    billing_dest_deliver.remove(billing_msg);

    mailer->add(message);
  }

  delete billing_msg;
  checkState();
}

void KernelPrivate::send(SrcSubmitSm *message) {
  transform(message);

  filter(message);

  mailer->add(message);
}

void KernelPrivate::dmResponced(SrcDeliverSm *message) {
  SrcSessionSettings *settings(
    Kernel::instance()->getSrcSessionSettings(message->src_session_id));

  if(message->error_code == smpp::parameters::command_status::ESME_ROK ||
     message->attempt >= settings->delivery_attempts) {
    if(requestBilling(message)) {
      return;
    }
  }

  mailer->add(message);
}

void KernelPrivate::dmResponced(DestDeliverSm *message) {
  if(message->message_state == smpp::parameters::message_state::DELIVERED) {
    if(requestBilling(message)) {
      return;
    }
  }

  mailer->add(message);
}

void KernelPrivate::mailerStateChanged(State state) {
  switch(state) {
    case ERROR:
      mailer->stop();
      break;
    case STARTED:
      session_manager->start();
      break;
    case STOPED:
      checkState();
      break;
    default:
      break;
  }
}

void KernelPrivate::sessionManagerStateChanged(State state) {
  switch(state) {
    case ERROR:
      session_manager->stop();
      mailer->stop();
      break;
    case STARTED:
      setState(STARTED);
      break;
    case STOPED:
      mailer->stop();
      break;
    default:
      break;
  }
}


void KernelPrivate::initKernelSettingsExecuted() {
  dbmanager::SqlQuery *q(qobject_cast<dbmanager::SqlQuery *>(sender()));

  if(!q) {
    return;
  }

  queries.removeAll(q);

  if(q->success()) {
    QList<QSqlRecord *> res(q->results());
    if(res.isEmpty()) {
      if(log_level >= smslog::ERROR) {
        LogEncoder::instance()->encode(smslog::ERROR,
                                       logclass::KERNEL,
                                       logclass::KERNEL,
                                       logmsgcodes::K_INIT_ERR);
      }
      setState(STOPED);
    } else if(state == STARTING) {
      QSqlRecord *r(res.takeFirst());

      settings->rpc_ip = QHostAddress(r->value(0).toString());
      settings->rpc_port = r->value(1).toUInt();
      settings->smpp_ip = QHostAddress(r->value(2).toString());
      settings->smpp_port = r->value(3).toUInt();
      settings->def_max_in_speed = r->value(4).toInt();
      settings->def_max_out_speed = r->value(5).toInt();
      settings->def_session_init_time = r->value(6).toInt();
      settings->def_enquire_link_time = r->value(7).toInt();
      settings->def_inactivity_time = r->value(8).toInt();
      settings->def_transaction_time = r->value(9).toInt();
      settings->def_max_in_trans = r->value(10).toInt();
      settings->def_max_out_trans = r->value(11).toInt();
      settings->name = r->value(12).toString();
      settings->last_message_id = r->value(13).toUInt();
      settings->billing_ip = QHostAddress(r->value(14).toString());
      settings->billing_port = r->value(15).toUInt();
      settings->state = NO_UPDATED;

      checkState();
    } else {
      setState(STOPED);
    }
  } else {
    if(log_level >= smslog::WARNING) {
      QMultiHash<int, QVariant> params;
      params.insert(logparam::ACTION_P, QString("initKernelSettings"));
      params.insert(logparam::ERROR_CODE_P, q->getFaultCode());
      params.insert(logparam::ERROR_MSG_P, q->getFaultString());

      LogEncoder::instance()->encode(smslog::WARNING,
                                     logclass::KERNEL,
                                     logclass::KERNEL,
                                     logmsgcodes::ERROR_EXEC_SQL_QUERY,
                                     params);
    }

    setState(STOPED);
  }

  delete q;
}

void KernelPrivate::initDeliveryProfilesExecuted() {
  dbmanager::SqlQuery *q(qobject_cast<dbmanager::SqlQuery *>(sender()));

  if(!q) {
    return;
  }

  queries.removeAll(q);

  if(q->success()) {
    QList<QSqlRecord *> res(q->results());
    QString dp_id;

    while(!res.isEmpty()) {
      QSqlRecord *r(res.takeFirst());

      quint32 id(r->value(0).toUInt());

      DeliveryProfilesRecord *dp_rec(
        new DeliveryProfilesRecord(id, r->value(1).toString(), NO_UPDATED));

      delivery_profiles.push_back(dp_rec);

      if(!dp_id.isEmpty()) {
        dp_id += ',';
      }

      dp_id += QString::number(id);
    }

    if(!dp_id.isEmpty()) {
      dbmanager::SqlQuery *query = new dbmanager::SqlQuery;

      query->add(QString("SELECT dc.`id`,"
                         "dc.`delivery_profile_id`,"
                         "dc.`error_code`,"
                         "dc.`description`,"
                         "dc.`timeout` "
                         "FROM `delivery_error_codes` dc "
                         "WHERE dc.`delivery_profile_id` IN (%1)")
                 .arg(Kernel::instance()->getKernelId()));

      connect(query, SIGNAL(executed()),
              this, SLOT(initDeliveryErrorExecuted()));

      queries.push_back(query);

      settings_db->execute(query);
    }
  } else {
    if(log_level >= smslog::WARNING) {
      QMultiHash<int, QVariant> params;
      params.insert(logparam::ACTION_P, QString("initDeliveryProfiles"));
      params.insert(logparam::ERROR_CODE_P, q->getFaultCode());
      params.insert(logparam::ERROR_MSG_P, q->getFaultString());

      LogEncoder::instance()->encode(smslog::WARNING,
                                     logclass::KERNEL,
                                     logclass::KERNEL,
                                     logmsgcodes::ERROR_EXEC_SQL_QUERY,
                                     params);
    }

    setState(STOPED);
  }

  delete q;
}

void KernelPrivate::initRouteTableExecuted() {
  dbmanager::SqlQuery *q(qobject_cast<dbmanager::SqlQuery *>(sender()));

  if(!q) {
    return;
  }

  queries.removeAll(q);

  if(q->success()) {
    QList<QSqlRecord *> res(q->results());

    while(!res.isEmpty()) {
      QSqlRecord *r(res.takeFirst());

      RouteTableRecord *rt_rec(
        new RouteTableRecord(r->value(0).toUInt(),
                             r->value(1).toUInt(),
                             r->value(2).toUInt(),
                             QRegExp(r->value(3).toString()),
                             QRegExp(r->value(4).toString()),
                             r->value(5).toUInt(),
                             NO_UPDATED));

      route_table.push_back(rt_rec);
    }

    checkState();
  } else {
    if(log_level >= smslog::WARNING) {
      QMultiHash<int, QVariant> params;
      params.insert(logparam::ACTION_P, QString("initRouteTable"));
      params.insert(logparam::ERROR_CODE_P, q->getFaultCode());
      params.insert(logparam::ERROR_MSG_P, q->getFaultString());

      LogEncoder::instance()->encode(smslog::WARNING,
                                     logclass::KERNEL,
                                     logclass::KERNEL,
                                     logmsgcodes::ERROR_EXEC_SQL_QUERY,
                                     params);
    }

    setState(STOPED);
  }

  delete q;
}

void KernelPrivate::initPoolTranslatorExecuted() {
  dbmanager::SqlQuery *q(qobject_cast<dbmanager::SqlQuery *>(sender()));

  if(!q) {
    return;
  }

  queries.removeAll(q);

  if(q->success()) {
    QList<QSqlRecord *> res(q->results());

    while(!res.isEmpty()) {
      QSqlRecord *r(res.takeFirst());

      PoolTranslatorRecord *st_rec(
        new PoolTranslatorRecord(r->value(0).toUInt(),
                                 r->value(1).toULongLong(),
                                 r->value(2).toULongLong(),
                                 NO_UPDATED));

      pool_translator.push_back(st_rec);
    }

    checkState();
  } else {
    if(log_level >= smslog::WARNING) {
      QMultiHash<int, QVariant> params;
      params.insert(logparam::ACTION_P, QString("initPoolTranslator"));
      params.insert(logparam::ERROR_CODE_P, q->getFaultCode());
      params.insert(logparam::ERROR_MSG_P, q->getFaultString());

      LogEncoder::instance()->encode(smslog::WARNING,
                                     logclass::KERNEL,
                                     logclass::KERNEL,
                                     logmsgcodes::ERROR_EXEC_SQL_QUERY,
                                     params);
    }

    setState(STOPED);
  }

  delete q;
}

void KernelPrivate::initSrcTranslatorExecuted() {
  dbmanager::SqlQuery *q(qobject_cast<dbmanager::SqlQuery *>(sender()));

  if(!q) {
    return;
  }

  queries.removeAll(q);

  if(q->success()) {
    QList<QSqlRecord *> res(q->results());

    while(!res.isEmpty()) {
      QSqlRecord *r(res.takeFirst());

      SrcTranslatorRecord *st_rec(
        new SrcTranslatorRecord(r->value(0).toUInt(),
                                r->value(1).toUInt(),
                                r->value(2).toUInt(),
                                QRegExp(r->value(3).toString()),
                                QRegExp(r->value(4).toString()),
                                r->value(5).toString(),
                                r->value(6).toUInt(),
                                r->value(7).toUInt(),
                                r->value(8).toUInt(),
                                NO_UPDATED));

      src_translator.push_back(st_rec);
    }

    checkState();
  } else {
    if(log_level >= smslog::WARNING) {
      QMultiHash<int, QVariant> params;
      params.insert(logparam::ACTION_P, QString("initSrcTranslator"));
      params.insert(logparam::ERROR_CODE_P, q->getFaultCode());
      params.insert(logparam::ERROR_MSG_P, q->getFaultString());

      LogEncoder::instance()->encode(smslog::WARNING,
                                     logclass::KERNEL,
                                     logclass::KERNEL,
                                     logmsgcodes::ERROR_EXEC_SQL_QUERY,
                                     params);
    }

    setState(STOPED);
  }

  delete q;
}

void KernelPrivate::initSrcSessionsExecuted() {
  dbmanager::SqlQuery *q(qobject_cast<dbmanager::SqlQuery *>(sender()));

  if(!q) {
    return;
  }

  queries.removeAll(q);

  if(q->success()) {
    QList<QSqlRecord *> res(q->results());

    while(!res.isEmpty()) {
      QSqlRecord *r(res.takeFirst());

      SrcSessionSettings *ss_rec(
        new SrcSessionSettings(r->value(0).toUInt(),
                               r->value(1).toString(),
                               r->value(2).toInt(),
                               r->value(3).toInt(),
                               r->value(4).toInt(),
                               r->value(5).toInt(),
                               r->value(6).toInt(),
                               r->value(7).toInt(),
                               r->value(8).toInt(),
                               r->value(9).toInt(),
                               r->value(10).toUInt(),
                               r->value(11).toString(),
                               r->value(12).toString(),
                               r->value(13).toUInt(),
                               r->value(14).toBool(),
                               QRegExp(r->value(15).toString()),
                               QRegExp(r->value(16).toString()),
                               QRegExp(r->value(17).toString()),
                               QRegExp(r->value(18).toString()),
                               r->value(19).toUInt(),
                               r->value(20).toUInt(),
                               NO_UPDATED));

      src_sessions_settings.push_back(ss_rec);
    }

    checkState();
  } else {
    if(log_level >= smslog::WARNING) {
      QMultiHash<int, QVariant> params;
      params.insert(logparam::ACTION_P, QString("initSrcSessions"));
      params.insert(logparam::ERROR_CODE_P, q->getFaultCode());
      params.insert(logparam::ERROR_MSG_P, q->getFaultString());

      LogEncoder::instance()->encode(smslog::WARNING,
                                     logclass::KERNEL,
                                     logclass::KERNEL,
                                     logmsgcodes::ERROR_EXEC_SQL_QUERY,
                                     params);
    }

    setState(STOPED);
  }

  delete q;
}

void KernelPrivate::initDestSessionsExecuted() {
  dbmanager::SqlQuery *q(qobject_cast<dbmanager::SqlQuery *>(sender()));

  if(!q) {
    return;
  }

  queries.removeAll(q);

  if(q->success()) {
    QList<QSqlRecord *> res(q->results());

    while(!res.isEmpty()) {
      QSqlRecord *r(res.takeFirst());

      DestSessionSettings *ds_rec(
        new DestSessionSettings(r->value(0).toUInt(),
                                r->value(1).toString(),
                                QHostAddress(r->value(2).toString()),
                                r->value(3).toUInt(),
                                r->value(4).toInt(),
                                r->value(5).toInt(),
                                r->value(6).toInt(),
                                r->value(7).toInt(),
                                r->value(8).toInt(),
                                r->value(9).toInt(),
                                r->value(10).toInt(),
                                r->value(11).toInt(),
                                r->value(12).toString(),
                                r->value(13).toString(),
                                r->value(14).toString(),
                                QRegExp(r->value(15).toString()),
                                QRegExp(r->value(16).toString()),
                                QRegExp(r->value(17).toString()),
                                ((r->value(18).toString() == "HEX") ? HEX : NUM),
                                ((r->value(19).toString() == "HEX") ? HEX : NUM),
                                r->value(20).toInt(),
                                r->value(21).toUInt(),
                                r->value(22).toBool(),
                                QRegExp(r->value(23).toString()),
                                QRegExp(r->value(24).toString()),
                                QRegExp(r->value(25).toString()),
                                QRegExp(r->value(26).toString()),
                                r->value(27).toUInt(),
                                r->value(28).toUInt(),
                                NO_UPDATED));

      dest_sessions_settings.push_back(ds_rec);
    }

    checkState();
  } else {
    if(log_level >= smslog::WARNING) {
      QMultiHash<int, QVariant> params;
      params.insert(logparam::ACTION_P, QString("initDestSessions"));
      params.insert(logparam::ERROR_CODE_P, q->getFaultCode());
      params.insert(logparam::ERROR_MSG_P, q->getFaultString());

      LogEncoder::instance()->encode(smslog::WARNING,
                                     logclass::KERNEL,
                                     logclass::KERNEL,
                                     logmsgcodes::ERROR_EXEC_SQL_QUERY,
                                     params);
    }

    setState(STOPED);
  }

  delete q;
}

void KernelPrivate::initDeliveryErrorExecuted() {
  dbmanager::SqlQuery *q(qobject_cast<dbmanager::SqlQuery *>(sender()));

  if(!q) {
    return;
  }

  queries.removeAll(q);

  if(q->success()) {
    QList<QSqlRecord *> res(q->results());

    while(!res.isEmpty()) {
      QSqlRecord *r(res.takeFirst());

      DeliveryErrorCodesRecord *dec_rec(
        new DeliveryErrorCodesRecord(r->value(0).toUInt(),
                                     r->value(1).toUInt(),
                                     r->value(2).toUInt(),
                                     r->value(3).toString(),
                                     r->value(4).toInt(),
                                     NO_UPDATED));

      delivery_error_codes.push_back(dec_rec);
    }

    checkState();
  } else {
    if(log_level >= smslog::WARNING) {
      QMultiHash<int, QVariant> params;
      params.insert(logparam::ACTION_P, QString("initDeliveryErrorCodes"));
      params.insert(logparam::ERROR_CODE_P, q->getFaultCode());
      params.insert(logparam::ERROR_MSG_P, q->getFaultString());

      LogEncoder::instance()->encode(smslog::WARNING,
                                     logclass::KERNEL,
                                     logclass::KERNEL,
                                     logmsgcodes::ERROR_EXEC_SQL_QUERY,
                                     params);
    }

    setState(STOPED);
  }

  delete q;
}

void KernelPrivate::writeChangeKernelSettings() {
  dbmanager::SqlQuery *q(qobject_cast<dbmanager::SqlQuery *>(sender()));

  if(!q) {
    return;
  }

  queries.removeAll(q);

  if(!q->success()) {
    if(log_level >= smslog::WARNING) {
      QMultiHash<int, QVariant> params;
      params.insert(logparam::ACTION_P, QString("writeChangeKernelSettings"));
      params.insert(logparam::ERROR_CODE_P, q->getFaultCode());
      params.insert(logparam::ERROR_MSG_P, q->getFaultString());

      LogEncoder::instance()->encode(smslog::WARNING,
                                     logclass::KERNEL,
                                     logclass::KERNEL,
                                     logmsgcodes::ERROR_EXEC_SQL_QUERY,
                                     params);
    }

    setState(STOPED);
  }

  delete q;

  checkState();
}

PoolTranslatorRecord *KernelPrivate::getPoolTranslator(quint32 id) const {
  for(int i = 0; i < pool_translator.size(); i++) {
    PoolTranslatorRecord *rec(pool_translator[i]);
    if(rec->id == id) {
      return rec;
    }
  }

  return 0;
}

smslog::LogLevel KernelPrivate::getLogLevel() {
  QReadLocker locker(&lock);
  return log_level;
}

void KernelPrivate::writeChangeToDatabase() {
  if(settings->state == UPDATED) {
    dbmanager::SqlQuery *query = new dbmanager::SqlQuery(false, true);

    queries.push_back(query);

    connect(query, SIGNAL(executed()),
            this, SLOT(writeChangeKernelSettings()));

    query->add(QString(
      "UPDATE `kernels` "
      "SET `rpc_ip`='%1',"
      "`rpc_port`=%2,"
      "`smpp_ip`='%3',"
      "`smpp_port`=%4,"
      "`def_max_in_speed`=%5,"
      "`def_max_out_speed`=%6,"
      "`def_session_init_time`=%7,"
      "`def_enquire_link_time`=%8,"
      "`def_inactivity_time`=%9,"
      "`def_transaction_time`=%10,"
      "`def_max_in_trans`=%11,"
      "`def_max_out_trans`=%12,"
      "`name`='%13',"
      "`last_message_id`=%14,"
      "`billing_ip`='%15',"
      "`billing_port`=%16 "
      "WHERE `id`=%17")
        .arg(settings->rpc_ip.toString())
        .arg(settings->rpc_port)
        .arg(settings->smpp_ip.toString())
        .arg(settings->smpp_port)
        .arg(settings->def_max_in_speed)
        .arg(settings->def_max_out_speed)
        .arg(settings->def_session_init_time)
        .arg(settings->def_enquire_link_time)
        .arg(settings->def_inactivity_time)
        .arg(settings->def_transaction_time)
        .arg(settings->def_max_in_trans)
        .arg(settings->def_max_out_trans)
        .arg(settings->name)
        .arg(settings->last_message_id)
        .arg(settings->billing_ip.toString())
        .arg(settings->billing_port)
        .arg(settings->id));

    settings_db->execute(query);
  }
}

void KernelPrivate::transform(SrcSubmitSm *message) {
  quint8 rd_masked(message->sm.getRegisteredDelivery() & 0x03);

  if(rd_masked == smpp::parameters::registered_delivery::NO_DELIVERY) {
    message->sm.setRegisteredDelivery(
      smpp::parameters::registered_delivery::NO_DELIVERY);
  } else {
    message->sm.setRegisteredDelivery(
      smpp::parameters::registered_delivery::DELIVERY_ALL);
  }

  QString src_addr(QString("%1:%2:%3")
    .arg(message->sm.getSourceAddr())
    .arg(message->sm.getSourceAddrTon())
    .arg(message->sm.getSourceAddrNpi()));

  QString dest_addr(QString("%1:%2:%3")
    .arg(message->sm.getDestinationAddr())
    .arg(message->sm.getDestAddrTon())
    .arg(message->sm.getDestAddrNpi()));

  for(int i = 0; i < src_translator.size(); i++) {
    SrcTranslatorRecord *rule(src_translator[i]);
    if(rule->src_session_id == message->src_session_id &&
       rule->dest_session_id == message->dest_session_id &&
       rule->src_addr.exactMatch(src_addr) &&
       rule->dest_addr.exactMatch(dest_addr)) {
      QString src_addr_after(rule->src_addr_after);

      if(rule->pool_id != 0) {
        PoolTranslatorRecord *rec(getPoolTranslator(rule->pool_id));
        if(rec) {
          int delta(rec->end_addr - rec->begin_addr + 1);
          quint64 rand_number((quint64)qrand()%delta);
          src_addr_after = QString::number(rand_number + rec->begin_addr);
        }
      }

      message->src_addr_after = src_addr_after;
      message->src_addr_ton_after = rule->src_ton_after;
      message->src_addr_npi_after = rule->src_npi_after;
      if(Kernel::instance()->getLogLevel() == smslog::DEBUG) {
        QMultiHash<int, QVariant> params;
        params.insert(logparam::SRC_MESSAGE_ID_P, message->src_message_id);
        params.insert(logparam::SRC_ADDR_P, message->sm.getSourceAddr());
        params.insert(logparam::SRC_ADDR_TON_P,
                      message->sm.getSourceAddrTon());
        params.insert(logparam::SRC_ADDR_NPI_P,
                      message->sm.getSourceAddrNpi());
        params.insert(logparam::SRC_ADDR_P, message->src_addr_after);
        params.insert(logparam::SRC_ADDR_TON_P,
                      message->src_addr_ton_after);
        params.insert(logparam::SRC_ADDR_NPI_P,
                      message->src_addr_npi_after);
        params.insert(logparam::DEST_ADDR_P,
                      message->sm.getDestinationAddr());
        params.insert(logparam::DEST_ADDR_TON_P,
                      message->sm.getDestAddrTon());
        params.insert(logparam::DEST_ADDR_NPI_P,
                      message->sm.getDestAddrNpi());
        LogEncoder::instance()->encode(smslog::DEBUG,
                                       logclass::KERNEL,
                                       logclass::KERNEL,
                                       logmsgcodes::K_TRANSFORM_SRC_SUBMIT,
                                       params);
      }
      break;
    }
  }
}

void KernelPrivate::filter(SrcSubmitSm *message) {
  SrcSessionSettings *src_session_settings(
    Kernel::instance()->getSrcSessionSettings(message->src_session_id));

  QString src_addr(message->src_addr_after);

  if(src_session_settings->src_white_list.exactMatch(src_addr) &&
     !src_session_settings->src_black_list.exactMatch(src_addr)) {
    src_addr += QString(":%1").arg(message->src_addr_ton_after);
    if(src_session_settings->src_white_list.exactMatch(src_addr) &&
       !src_session_settings->src_black_list.exactMatch(src_addr)) {
      src_addr += QString(":%1").arg(message->src_addr_npi_after);
      if(!src_session_settings->src_white_list.exactMatch(src_addr) &&
         src_session_settings->src_black_list.exactMatch(src_addr)) {
        message->message_state = smpp::parameters::message_state::REJECTED;
        message->error_code = smpp::parameters::command_status::ESME_RINVSRCNPI;
        if(Kernel::instance()->getLogLevel() == smslog::DEBUG) {
          QMultiHash<int, QVariant> params;
          params.insert(logparam::SRC_MESSAGE_ID_P, message->src_message_id);
          params.insert(logparam::ERROR_CODE_P, message->error_code);

          LogEncoder::instance()->encode(smslog::DEBUG,
                                         logclass::KERNEL,
                                         logclass::KERNEL,
                                         logmsgcodes::K_FILTER_SRC_SUBMIT,
                                         params);
        }

        return;
      }
    } else {
      message->message_state = smpp::parameters::message_state::REJECTED;
      message->error_code = smpp::parameters::command_status::ESME_RINVSRCTON;
      if(Kernel::instance()->getLogLevel() == smslog::DEBUG) {
        QMultiHash<int, QVariant> params;
        params.insert(logparam::SRC_MESSAGE_ID_P, message->src_message_id);
        params.insert(logparam::ERROR_CODE_P, message->error_code);

        LogEncoder::instance()->encode(smslog::DEBUG,
                                       logclass::KERNEL,
                                       logclass::KERNEL,
                                       logmsgcodes::K_FILTER_SRC_SUBMIT,
                                       params);
      }

      return;
    }
  } else {
    message->message_state = smpp::parameters::message_state::REJECTED;
    message->error_code = smpp::parameters::command_status::ESME_RINVSRCADR;
    if(Kernel::instance()->getLogLevel() == smslog::DEBUG) {
      QMultiHash<int, QVariant> params;
      params.insert(logparam::SRC_MESSAGE_ID_P, message->src_message_id);
      params.insert(logparam::ERROR_CODE_P, message->error_code);

      LogEncoder::instance()->encode(smslog::DEBUG,
                                     logclass::KERNEL,
                                     logclass::KERNEL,
                                     logmsgcodes::K_FILTER_SRC_SUBMIT,
                                     params);
    }

    return;
  }

  QString dest_addr(message->sm.getDestinationAddr());

  if(src_session_settings->dest_white_list.exactMatch(dest_addr) &&
     !src_session_settings->dest_black_list.exactMatch(dest_addr)) {
    dest_addr += QString(":%1").arg(message->sm.getDestAddrTon());
    if(src_session_settings->dest_white_list.exactMatch(dest_addr) &&
       !src_session_settings->dest_black_list.exactMatch(dest_addr)) {
      dest_addr += QString(":%1").arg(message->sm.getDestAddrNpi());
      if(!src_session_settings->dest_white_list.exactMatch(dest_addr) &&
         src_session_settings->dest_black_list.exactMatch(dest_addr)) {
        message->message_state = smpp::parameters::message_state::REJECTED;
        message->error_code = smpp::parameters::command_status::ESME_RINVDSTNPI;
        if(Kernel::instance()->getLogLevel() == smslog::DEBUG) {
          QMultiHash<int, QVariant> params;
          params.insert(logparam::SRC_MESSAGE_ID_P, message->src_message_id);
          params.insert(logparam::ERROR_CODE_P, message->error_code);

          LogEncoder::instance()->encode(smslog::DEBUG,
                                         logclass::KERNEL,
                                         logclass::KERNEL,
                                         logmsgcodes::K_FILTER_SRC_SUBMIT,
                                         params);
        }
      }
    } else {
      message->message_state = smpp::parameters::message_state::REJECTED;
      message->error_code = smpp::parameters::command_status::ESME_RINVDSTTON;
      if(Kernel::instance()->getLogLevel() == smslog::DEBUG) {
        QMultiHash<int, QVariant> params;
        params.insert(logparam::SRC_MESSAGE_ID_P, message->src_message_id);
        params.insert(logparam::ERROR_CODE_P, message->error_code);

        LogEncoder::instance()->encode(smslog::DEBUG,
                                       logclass::KERNEL,
                                       logclass::KERNEL,
                                       logmsgcodes::K_FILTER_SRC_SUBMIT,
                                       params);
      }
    }
  } else {
    message->message_state = smpp::parameters::message_state::REJECTED;
    message->error_code = smpp::parameters::command_status::ESME_RINVDSTADR;
    if(Kernel::instance()->getLogLevel() == smslog::DEBUG) {
      QMultiHash<int, QVariant> params;
      params.insert(logparam::SRC_MESSAGE_ID_P, message->src_message_id);
      params.insert(logparam::ERROR_CODE_P, message->error_code);

      LogEncoder::instance()->encode(smslog::DEBUG,
                                     logclass::KERNEL,
                                     logclass::KERNEL,
                                     logmsgcodes::K_FILTER_SRC_SUBMIT,
                                     params);
    }
  }
}

void KernelPrivate::filter(DestSubmitSm *message) {
  DestSessionSettings *dest_session_settings(
    Kernel::instance()->getDestSessionSettings(message->dest_session_id));

  QString src_addr(message->sm.getSourceAddr());

  if(dest_session_settings->src_white_list.exactMatch(src_addr) &&
     !dest_session_settings->src_black_list.exactMatch(src_addr)) {
    src_addr += QString(":%1").arg(message->sm.getSourceAddrTon());
    if(dest_session_settings->src_white_list.exactMatch(src_addr) &&
       !dest_session_settings->src_black_list.exactMatch(src_addr)) {
      src_addr += QString(":%1").arg(message->sm.getSourceAddrNpi());
      if(!dest_session_settings->src_white_list.exactMatch(src_addr) &&
         dest_session_settings->src_black_list.exactMatch(src_addr)) {
        message->error_code = smpp::parameters::command_status::ESME_RINVSRCNPI;
        if(Kernel::instance()->getLogLevel() == smslog::DEBUG) {
          QMultiHash<int, QVariant> params;
          params.insert(logparam::SRC_MESSAGE_ID_P, message->src_message_id);
          params.insert(logparam::ERROR_CODE_P, message->error_code);

          LogEncoder::instance()->encode(smslog::DEBUG,
                                         logclass::KERNEL,
                                         logclass::KERNEL,
                                         logmsgcodes::K_FILTER_DEST_SUBMIT,
                                         params);
        }

        return;
      }
    } else {
      message->error_code = smpp::parameters::command_status::ESME_RINVSRCTON;
      if(Kernel::instance()->getLogLevel() == smslog::DEBUG) {
        QMultiHash<int, QVariant> params;
        params.insert(logparam::SRC_MESSAGE_ID_P, message->src_message_id);
        params.insert(logparam::ERROR_CODE_P, message->error_code);

        LogEncoder::instance()->encode(smslog::DEBUG,
                                       logclass::KERNEL,
                                       logclass::KERNEL,
                                       logmsgcodes::K_FILTER_DEST_SUBMIT,
                                       params);
      }

      return;
    }
  } else {
    message->error_code = smpp::parameters::command_status::ESME_RINVSRCADR;
    if(Kernel::instance()->getLogLevel() == smslog::DEBUG) {
      QMultiHash<int, QVariant> params;
      params.insert(logparam::SRC_MESSAGE_ID_P, message->src_message_id);
      params.insert(logparam::ERROR_CODE_P, message->error_code);

      LogEncoder::instance()->encode(smslog::DEBUG,
                                     logclass::KERNEL,
                                     logclass::KERNEL,
                                     logmsgcodes::K_FILTER_DEST_SUBMIT,
                                     params);
    }

    return;
  }

  QString dest_addr(message->sm.getDestinationAddr());

  if(dest_session_settings->dest_white_list.exactMatch(dest_addr) &&
     !dest_session_settings->dest_black_list.exactMatch(dest_addr)) {
    dest_addr += QString(":%1").arg(message->sm.getDestAddrTon());
    if(dest_session_settings->dest_white_list.exactMatch(dest_addr) &&
       !dest_session_settings->dest_black_list.exactMatch(dest_addr)) {
      dest_addr += QString(":%1").arg(message->sm.getDestAddrNpi());
      if(!dest_session_settings->dest_white_list.exactMatch(dest_addr) &&
         dest_session_settings->dest_black_list.exactMatch(dest_addr)) {
        message->error_code = smpp::parameters::command_status::ESME_RINVDSTNPI;
        if(Kernel::instance()->getLogLevel() == smslog::DEBUG) {
          QMultiHash<int, QVariant> params;
          params.insert(logparam::SRC_MESSAGE_ID_P, message->src_message_id);
          params.insert(logparam::ERROR_CODE_P, message->error_code);

          LogEncoder::instance()->encode(smslog::DEBUG,
                                         logclass::KERNEL,
                                         logclass::KERNEL,
                                         logmsgcodes::K_FILTER_DEST_SUBMIT,
                                         params);
        }
      }
    } else {
      message->error_code = smpp::parameters::command_status::ESME_RINVDSTTON;
      if(Kernel::instance()->getLogLevel() == smslog::DEBUG) {
        QMultiHash<int, QVariant> params;
        params.insert(logparam::SRC_MESSAGE_ID_P, message->src_message_id);
        params.insert(logparam::ERROR_CODE_P, message->error_code);

        LogEncoder::instance()->encode(smslog::DEBUG,
                                       logclass::KERNEL,
                                       logclass::KERNEL,
                                       logmsgcodes::K_FILTER_DEST_SUBMIT,
                                       params);
      }
    }
  } else {
    message->error_code = smpp::parameters::command_status::ESME_RINVDSTADR;
    if(Kernel::instance()->getLogLevel() == smslog::DEBUG) {
      QMultiHash<int, QVariant> params;
      params.insert(logparam::SRC_MESSAGE_ID_P, message->src_message_id);
      params.insert(logparam::ERROR_CODE_P, message->error_code);

      LogEncoder::instance()->encode(smslog::DEBUG,
                                     logclass::KERNEL,
                                     logclass::KERNEL,
                                     logmsgcodes::K_FILTER_DEST_SUBMIT,
                                     params);
    }
  }
}

bool KernelPrivate::requestBilling(SrcSubmitSm *message) {
  SrcSessionSettings *src_session_settings(
    Kernel::instance()->getSrcSessionSettings(message->src_session_id));

  if(!src_session_settings) {
    return false;
  }

  if(src_session_settings->billing_resource_id != 0) {
    QVariantList params;
    int num_sms(-1);
    QVariant resource_id(src_session_settings->billing_resource_id);
    QVariant num_resource(num_sms);

    params.push_back(resource_id);
    params.push_back(message->sm.getDestinationAddr());
    params.push_back(num_resource);

    binaryrpc::ClientMessage *billing_msg =
      new binaryrpc::ClientMessage("changeResource", params);

    connect(billing_msg, SIGNAL(done(binaryrpc::ClientMessage *,
                                     const QVariant &)),
            this, SLOT(doneSrcSubmitBilling(binaryrpc::ClientMessage *,
                                            const QVariant &)));
    connect(billing_msg, SIGNAL(fault(binaryrpc::ClientMessage *,
                                      int, const QString &)),
            this, SLOT(faultSrcSubmitBilling(binaryrpc::ClientMessage *,
                                             int, const QString &)));

    billing_src_submit.insert(billing_msg, message);

    if(Kernel::instance()->getLogLevel() == smslog::DEBUG) {
      QMultiHash<int, QVariant> params;

      binaryrpc::BinaryEncoder encoder;

      encoder.resize(binaryrpc::getSize(resource_id));

      encoder << resource_id;

      params.insert(logparam::RPC_METHOD_NAME_P, billing_msg->name());
      params.insert(logparam::RPC_METHOD_PAR_P, encoder.getBuff());

      encoder.resize(binaryrpc::getSize(num_resource));
      encoder << num_resource;

      params.insert(logparam::RPC_METHOD_PAR_P, encoder.getBuff());
      params.insert(logparam::PEER_IP_P,
                    billing_client->peerAddress().toString());
      params.insert(logparam::PEER_PORT_P, billing_client->peerPort());

      LogEncoder::instance()->encode(smslog::DEBUG,
                                     logclass::KERNEL,
                                     logclass::KERNEL,
                                     logmsgcodes::K_BILLING_CL_REQUEST_2,
                                     params);
    }

    billing_client->send(billing_msg);

    return true;
  } else {
    return false;
  }
}

bool KernelPrivate::requestBilling(SrcDeliverSm *message) {
  SrcSessionSettings *src_session_settings(
    Kernel::instance()->getSrcSessionSettings(message->src_session_id));

  if(!src_session_settings) {
    return false;
  }

  if(src_session_settings->billing_resource_id != 0 &&
     src_session_settings->pay_only_delivered &&
     !message->dm.getShortMessage().contains("DELIVRD")) {
    QVariantList params;
    int num_sms(1);
    QVariant resource_id(src_session_settings->billing_resource_id);
    QVariant num_resource(num_sms);

    params.push_back(resource_id);
    params.push_back(message->dm.getSourceAddr());
    params.push_back(num_resource);

    binaryrpc::ClientMessage *billing_msg =
      new binaryrpc::ClientMessage("changeResource", params);

    connect(billing_msg, SIGNAL(done(binaryrpc::ClientMessage *,
                                     const QVariant &)),
            this, SLOT(doneSrcDeliverBilling(binaryrpc::ClientMessage *,
                                             const QVariant &)));
    connect(billing_msg, SIGNAL(fault(binaryrpc::ClientMessage *,
                                      int, const QString &)),
            this, SLOT(faultSrcDeliverBilling(binaryrpc::ClientMessage *,
                                              int, const QString &)));

    billing_src_deliver.insert(billing_msg, message);

    if(Kernel::instance()->getLogLevel() == smslog::DEBUG) {
      QMultiHash<int, QVariant> params;

      binaryrpc::BinaryEncoder encoder;

      encoder.resize(binaryrpc::getSize(resource_id));

      encoder << src_session_settings->billing_resource_id;

      params.insert(logparam::RPC_METHOD_NAME_P, billing_msg->name());
      params.insert(logparam::RPC_METHOD_PAR_P, encoder.getBuff());

      encoder.resize(binaryrpc::getSize(num_resource));

      encoder << num_resource;

      params.insert(logparam::RPC_METHOD_PAR_P, encoder.getBuff());
      params.insert(logparam::PEER_IP_P,
                    billing_client->peerAddress().toString());
      params.insert(logparam::PEER_PORT_P, billing_client->peerPort());

      LogEncoder::instance()->encode(smslog::DEBUG,
                                     logclass::KERNEL,
                                     logclass::KERNEL,
                                     logmsgcodes::K_BILLING_CL_REQUEST_2,
                                     params);
    }

    billing_client->send(billing_msg);

    return true;
  } else {
    return false;
  }
}

bool KernelPrivate::requestBilling(DestSubmitSm *message) {
  DestSessionSettings *dest_session_settings(
    Kernel::instance()->getDestSessionSettings(message->dest_session_id));

  if(!dest_session_settings) {
    return false;
  }

  if(dest_session_settings->billing_resource_id != 0) {
    QVariantList params;
    int num_sms(-1);
    QVariant resource_id(dest_session_settings->billing_resource_id);
    QVariant num_resource(num_sms);

    params.push_back(resource_id);
    params.push_back(message->sm.getDestinationAddr());
    params.push_back(num_resource);

    binaryrpc::ClientMessage *billing_msg =
      new binaryrpc::ClientMessage("changeResource", params);

    connect(billing_msg, SIGNAL(done(binaryrpc::ClientMessage *,
                                     const QVariant &)),
            this, SLOT(doneDestSubmitBilling(binaryrpc::ClientMessage *,
                                             const QVariant &)));
    connect(billing_msg, SIGNAL(fault(binaryrpc::ClientMessage *,
                                      int, const QString &)),
            this, SLOT(faultDestSubmitBilling(binaryrpc::ClientMessage *,
                                              int, const QString &)));

    billing_dest_submit.insert(billing_msg, message);

    if(Kernel::instance()->getLogLevel() == smslog::DEBUG) {
      QMultiHash<int, QVariant> params;

      binaryrpc::BinaryEncoder encoder;

      encoder.resize(binaryrpc::getSize(resource_id));

      encoder << resource_id;

      params.insert(logparam::RPC_METHOD_NAME_P, billing_msg->name());
      params.insert(logparam::RPC_METHOD_PAR_P, encoder.getBuff());

      encoder.resize(binaryrpc::getSize(num_resource));
      encoder << num_resource;

      params.insert(logparam::RPC_METHOD_PAR_P, encoder.getBuff());
      params.insert(logparam::PEER_IP_P,
                    billing_client->peerAddress().toString());
      params.insert(logparam::PEER_PORT_P, billing_client->peerPort());

      LogEncoder::instance()->encode(smslog::DEBUG,
                                     logclass::KERNEL,
                                     logclass::KERNEL,
                                     logmsgcodes::K_BILLING_CL_REQUEST_2,
                                     params);
    }

    billing_client->send(billing_msg);

    return true;
  } else {
    return false;
  }
}

bool KernelPrivate::requestBilling(DestDeliverSm *message) {
  DestSessionSettings *dest_session_settings(
    Kernel::instance()->getDestSessionSettings(message->dest_session_id));

  if(!dest_session_settings) {
    return false;
  }

  if(dest_session_settings->billing_resource_id != 0 &&
     dest_session_settings->pay_only_delivered &&
     !message->dm.getShortMessage().contains("DELIVRD")) {
    QVariantList params;
    int num_sms(1);
    QVariant resource_id(dest_session_settings->billing_resource_id);
    QVariant num_resource(num_sms);

    params.push_back(resource_id);
    params.push_back(message->dm.getSourceAddr());
    params.push_back(num_resource);

    binaryrpc::ClientMessage *billing_msg =
      new binaryrpc::ClientMessage("changeResource", params);

    connect(billing_msg, SIGNAL(done(binaryrpc::ClientMessage *,
                                     const QVariant &)),
            this, SLOT(doneDestDeliverBilling(binaryrpc::ClientMessage *,
                                              const QVariant &)));
    connect(billing_msg, SIGNAL(fault(binaryrpc::ClientMessage *,
                                      int, const QString &)),
            this, SLOT(faultDestDeliverBilling(binaryrpc::ClientMessage *,
                                               int, const QString &)));

    billing_dest_deliver.insert(billing_msg, message);

    if(Kernel::instance()->getLogLevel() == smslog::DEBUG) {
      QMultiHash<int, QVariant> params;

      binaryrpc::BinaryEncoder encoder;

      encoder.resize(binaryrpc::getSize(resource_id));

      encoder << dest_session_settings->billing_resource_id;

      params.insert(logparam::RPC_METHOD_NAME_P, billing_msg->name());
      params.insert(logparam::RPC_METHOD_PAR_P, encoder.getBuff());

      encoder.resize(binaryrpc::getSize(num_resource));

      encoder << num_resource;

      params.insert(logparam::RPC_METHOD_PAR_P, encoder.getBuff());
      params.insert(logparam::PEER_IP_P,
                    billing_client->peerAddress().toString());
      params.insert(logparam::PEER_PORT_P, billing_client->peerPort());

      LogEncoder::instance()->encode(smslog::DEBUG,
                                     logclass::KERNEL,
                                     logclass::KERNEL,
                                     logmsgcodes::K_BILLING_CL_REQUEST_2,
                                     params);
    }

    billing_client->send(billing_msg);

    return true;
  } else {
    return false;
  }
}

void KernelPrivate::route(SrcSubmitSm *message) {
  int def_i(-1);

  quint16 min_metric(999);
  quint32 dest_session_id(0);

  QString src_addr(QString("%1:%2:%3")
                      .arg(message->sm.getSourceAddr())
                      .arg(message->sm.getSourceAddrTon())
                      .arg(message->sm.getSourceAddrNpi()));

  QString dest_addr(QString("%1:%2:%3")
                      .arg(message->sm.getDestinationAddr())
                      .arg(message->sm.getDestAddrTon())
                      .arg(message->sm.getDestAddrNpi()));

  for(int i = 0; i < route_table.size(); i++) {
    RouteTableRecord *rec(route_table[i]);
    if(rec->src_session_id == message->src_session_id) {
      if(rec->metric < min_metric) {
        min_metric = rec->metric;
        def_i = i;
      }
      if(rec->src_addr.exactMatch(src_addr) &&
         rec->dest_addr.exactMatch(dest_addr)) {
        dest_session_id = rec->dest_session_id;
        break;
      }
    }
  }

  if(def_i != -1 && dest_session_id == 0) {
    dest_session_id = route_table[def_i]->dest_session_id;
  }

  message->dest_session_id = dest_session_id;

  if(Kernel::instance()->getLogLevel() == smslog::DEBUG) {
    QMultiHash<int, QVariant> params;
    params.insert(logparam::SRC_MESSAGE_ID_P, message->src_message_id);
    params.insert(logparam::SRC_SESSION_ID_P, message->src_session_id);
    params.insert(logparam::DEST_SESSION_ID_P, message->dest_session_id);

    LogEncoder::instance()->encode(smslog::DEBUG,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::K_SRC_SUBMIT_ROUTED,
                                   params);
  }
}

bool KernelPrivate::readConfig(QSettings *config) {
  config->beginGroup(KERNEL_KEY);

  bool res(config->contains(LOG_LEVEL_KEY));

  if(!res) {
    QMultiHash<int, QVariant> params;
    params.insert(logparam::CONF_ROOT_KEY_P, QString(KERNEL_KEY));
    params.insert(logparam::CONF_1_KEY_P, QString(LOG_LEVEL_KEY));
    LogEncoder::instance()->encode(smslog::ERROR,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::CONF_NOT_FOUND_KEY_1_2,
                                   params);
    config->endGroup();
    return res;
  }

  res = config->contains(KERNEL_ID_KEY);

  if(!res) {
    QMultiHash<int, QVariant> params;
    params.insert(logparam::CONF_ROOT_KEY_P, QString(KERNEL_KEY));
    params.insert(logparam::CONF_1_KEY_P, QString(KERNEL_ID_KEY));
    LogEncoder::instance()->encode(smslog::ERROR,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::CONF_NOT_FOUND_KEY_1_2,
                                   params);
    config->endGroup();
    return res;
  }

  res = config->contains(DB_HOST_KEY);

  if(!res) {
    QMultiHash<int, QVariant> params;
    params.insert(logparam::CONF_ROOT_KEY_P, QString(KERNEL_KEY));
    params.insert(logparam::CONF_1_KEY_P, QString(DB_HOST_KEY));
    LogEncoder::instance()->encode(smslog::ERROR,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::CONF_NOT_FOUND_KEY_1_2,
                                   params);
    config->endGroup();
    return res;
  }

  res = config->contains(DB_PORT_KEY);

  if(!res) {
    QMultiHash<int, QVariant> params;
    params.insert(logparam::CONF_ROOT_KEY_P, QString(KERNEL_KEY));
    params.insert(logparam::CONF_1_KEY_P, QString(DB_PORT_KEY));
    LogEncoder::instance()->encode(smslog::ERROR,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::CONF_NOT_FOUND_KEY_1_2,
                                   params);
    config->endGroup();
    return res;
  }

  res = config->contains(DB_NAME_KEY);

  if(!res) {
    QMultiHash<int, QVariant> params;
    params.insert(logparam::CONF_ROOT_KEY_P, QString(KERNEL_KEY));
    params.insert(logparam::CONF_1_KEY_P, QString(DB_NAME_KEY));
    LogEncoder::instance()->encode(smslog::ERROR,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::CONF_NOT_FOUND_KEY_1_2,
                                   params);
    config->endGroup();
    return res;
  }

  res = config->contains(DB_UNAME_KEY);

  if(!res) {
    QMultiHash<int, QVariant> params;
    params.insert(logparam::CONF_ROOT_KEY_P, QString(KERNEL_KEY));
    params.insert(logparam::CONF_1_KEY_P, QString(DB_UNAME_KEY));
    LogEncoder::instance()->encode(smslog::ERROR,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::CONF_NOT_FOUND_KEY_1_2,
                                   params);
    config->endGroup();
    return res;
  }

  res = config->contains(DB_PASS_KEY);

  if(!res) {
    QMultiHash<int, QVariant> params;
    params.insert(logparam::CONF_ROOT_KEY_P, QString(KERNEL_KEY));
    params.insert(logparam::CONF_1_KEY_P, QString(DB_PASS_KEY));
    LogEncoder::instance()->encode(smslog::ERROR,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::CONF_NOT_FOUND_KEY_1_2,
                                   params);
    config->endGroup();
    return res;
  }

  res = config->contains(NUM_CONNECT_DB);

  if(!res) {
    QMultiHash<int, QVariant> params;
    params.insert(logparam::CONF_ROOT_KEY_P, QString(KERNEL_KEY));
    params.insert(logparam::CONF_1_KEY_P, QString(NUM_CONNECT_DB));
    LogEncoder::instance()->encode(smslog::ERROR,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::CONF_NOT_FOUND_KEY_1_2,
                                   params);
    config->endGroup();
    return res;
  }

  lock.lockForWrite();
  log_level = (smslog::LogLevel)config->value(LOG_LEVEL_KEY).toInt();
  settings->id = config->value(KERNEL_ID_KEY).toInt();
  db_settings_host = config->value(DB_HOST_KEY).toString();
  db_settings_port = config->value(DB_PORT_KEY).toInt();
  db_settings_name = config->value(DB_NAME_KEY).toString();
  db_settings_uname = config->value(DB_UNAME_KEY).toString();
  db_settings_pass = config->value(DB_PASS_KEY).toString();
  num_connect_settings_db = config->value(NUM_CONNECT_DB).toInt();
  lock.unlock();

  config->endGroup();

  config->beginGroup(DIRECTIONS_KEY);

  res = config->contains(DB_HOST_KEY);

  if(!res) {
    QMultiHash<int, QVariant> params;
    params.insert(logparam::CONF_ROOT_KEY_P, QString(DIRECTIONS_KEY));
    params.insert(logparam::CONF_1_KEY_P, QString(DB_HOST_KEY));
    LogEncoder::instance()->encode(smslog::ERROR,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::CONF_NOT_FOUND_KEY_1_2,
                                   params);
    config->endGroup();
    return res;
  }

  res = config->contains(DB_PORT_KEY);

  if(!res) {
    QMultiHash<int, QVariant> params;
    params.insert(logparam::CONF_ROOT_KEY_P, QString(DIRECTIONS_KEY));
    params.insert(logparam::CONF_1_KEY_P, QString(DB_PORT_KEY));
    LogEncoder::instance()->encode(smslog::ERROR,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::CONF_NOT_FOUND_KEY_1_2,
                                   params);
    config->endGroup();
    return res;
  }

  res = config->contains(DB_NAME_KEY);

  if(!res) {
    QMultiHash<int, QVariant> params;
    params.insert(logparam::CONF_ROOT_KEY_P, QString(DIRECTIONS_KEY));
    params.insert(logparam::CONF_1_KEY_P, QString(DB_NAME_KEY));
    LogEncoder::instance()->encode(smslog::ERROR,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::CONF_NOT_FOUND_KEY_1_2,
                                   params);
    config->endGroup();
    return res;
  }

  res = config->contains(DB_UNAME_KEY);

  if(!res) {
    QMultiHash<int, QVariant> params;
    params.insert(logparam::CONF_ROOT_KEY_P, QString(DIRECTIONS_KEY));
    params.insert(logparam::CONF_1_KEY_P, QString(DB_UNAME_KEY));
    LogEncoder::instance()->encode(smslog::ERROR,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::CONF_NOT_FOUND_KEY_1_2,
                                   params);
    config->endGroup();
    return res;
  }

  res = config->contains(DB_PASS_KEY);

  if(!res) {
    QMultiHash<int, QVariant> params;
    params.insert(logparam::CONF_ROOT_KEY_P, QString(DIRECTIONS_KEY));
    params.insert(logparam::CONF_1_KEY_P, QString(DB_PASS_KEY));
    LogEncoder::instance()->encode(smslog::ERROR,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::CONF_NOT_FOUND_KEY_1_2,
                                   params);
    config->endGroup();
    return res;
  }

  res = config->contains(NUM_CONNECT_DB);

  if(!res) {
    QMultiHash<int, QVariant> params;
    params.insert(logparam::CONF_ROOT_KEY_P, QString(DIRECTIONS_KEY));
    params.insert(logparam::CONF_1_KEY_P, QString(NUM_CONNECT_DB));
    LogEncoder::instance()->encode(smslog::ERROR,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::CONF_NOT_FOUND_KEY_1_2,
                                   params);
    config->endGroup();
    return res;
  }

  lock.lockForWrite();
  db_directions_host = config->value(DB_HOST_KEY).toString();
  db_directions_port = config->value(DB_PORT_KEY).toInt();
  db_directions_name = config->value(DB_NAME_KEY).toString();
  db_directions_uname = config->value(DB_UNAME_KEY).toString();
  db_directions_pass = config->value(DB_PASS_KEY).toString();
  num_connect_directions_db = config->value(NUM_CONNECT_DB).toInt();
  lock.unlock();

  config->endGroup();

  config->beginGroup(MESSAGES_KEY);

  lock.lockForWrite();
  db_messages_host = config->value(DB_HOST_KEY).toString();
  db_messages_port = config->value(DB_PORT_KEY).toInt();
  db_messages_name = config->value(DB_NAME_KEY).toString();
  db_messages_uname = config->value(DB_UNAME_KEY).toString();
  db_messages_pass = config->value(DB_PASS_KEY).toString();
  num_connect_messages_db = config->value(NUM_CONNECT_DB).toInt();
  lock.unlock();

  config->endGroup();

  return res;
}

void KernelPrivate::checkState() {
  switch(state) {
    case STOPING:
      if(queries.isEmpty() &&
         rpc_server_sessions.isEmpty() &&
         billing_src_submit.isEmpty() &&
         billing_src_deliver.isEmpty()) {
        setState(STOPED);
      }
      break;
    case STARTING:
      if(queries.isEmpty()) {
        if(rpc_server->listen(settings->rpc_ip, settings->rpc_port)) {
          if(getLogLevel() >= smslog::INFO) {
            QMultiHash<int, QVariant> params;
            params.insert(logparam::LOCAL_IP_P, settings->rpc_ip.toString());
            params.insert(logparam::LOCAL_PORT_P, settings->rpc_port);

            LogEncoder::instance()->encode(smslog::INFO,
                                           logclass::KERNEL,
                                           logclass::KERNEL,
                                           logmsgcodes::K_RPC_SRV_LISTEN,
                                           params);
          }

          setState(STARTED);
        } else {
          if(getLogLevel() >= smslog::ERROR) {
            QMultiHash<int, QVariant> params;
            params.insert(logparam::LOCAL_IP_P, settings->rpc_ip.toString());
            params.insert(logparam::LOCAL_PORT_P, settings->rpc_port);
            params.insert(logparam::ERROR_CODE_P,
                          (int)rpc_server->serverError());
            params.insert(logparam::ERROR_MSG_P, rpc_server->errorString());

            LogEncoder::instance()->encode(smslog::ERROR,
                                           logclass::KERNEL,
                                           logclass::KERNEL,
                                           logmsgcodes::K_RPC_SRV_LISTEN,
                                           params);
          }

          setState(STOPING);
        }
      }
      break;
    default:
      break;
  }
}

void KernelPrivate::setState(State s) {
  if(state == s)
    return;

  switch(s) {
    case STOPING:
      if(state == STOPED) {
        return;
      } else {
        if(getLogLevel() >= smslog::INFO) {
          LogEncoder::instance()->encode(smslog::INFO,
                                         logclass::KERNEL,
                                         logclass::KERNEL,
                                         logmsgcodes::K_STOPING_STATE);
        }
        writeChangeToDatabase();
        session_manager->stop();
      }
      break;
    case STARTING:
      if(state == STARTED) {
        return;
      } else {
        if(getLogLevel() >= smslog::INFO) {
          LogEncoder::instance()->encode(smslog::INFO,
                                         logclass::KERNEL,
                                         logclass::KERNEL,
                                         logmsgcodes::K_STARTING_STATE);
        }
        initKernelSettings();
        initDeliveryProfiles();
        initRouteTable();
        initPoolTranslator();
        initSrcTranslator();
        initSrcSessions();
        initDestSessions();
      }
      break;
    case STOPED:
      billing_client->close();
      if(getLogLevel() >= smslog::INFO) {
        LogEncoder::instance()->encode(smslog::INFO,
                                       logclass::KERNEL,
                                       logclass::KERNEL,
                                       logmsgcodes::K_STOPED_STATE);
      }
      break;
    case STARTED:
      mailer->start();
      billing_client->open(settings->billing_ip, settings->billing_port);
      if(getLogLevel() >= smslog::INFO) {
        LogEncoder::instance()->encode(smslog::INFO,
                                       logclass::KERNEL,
                                       logclass::KERNEL,
                                       logmsgcodes::K_STARTED_STATE);
      }
      break;
    case ERROR:
      if(getLogLevel() >= smslog::INFO) {
        LogEncoder::instance()->encode(smslog::INFO,
                                       logclass::KERNEL,
                                       logclass::KERNEL,
                                       logmsgcodes::K_ERROR_STATE);
      }
      break;
  }

  state = s;

  emit Kernel::instance()->stateChanged(state);
}

void KernelPrivate::initKernelSettings() {
  dbmanager::SqlQuery *query = new dbmanager::SqlQuery;

  query->add(QString("SELECT `rpc_ip`,"
                     "`rpc_port`,"
                     "`smpp_ip`,"
                     "`smpp_port`,"
                     "`def_max_in_speed`,"
                     "`def_max_out_speed`,"
                     "`def_session_init_time`,"
                     "`def_enquire_link_time`,"
                     "`def_inactivity_time`,"
                     "`def_transaction_time`,"
                     "`def_max_in_trans`,"
                     "`def_max_out_trans`,"
                     "`name`,"
                     "`last_message_id`,"
                     "`billing_ip`,"
                     "`billing_port` "
                     "FROM `kernels` "
                     "WHERE `id`=%1 LIMIT 1")
             .arg(Kernel::instance()->getKernelId()));

  connect(query, SIGNAL(executed()),
          this, SLOT(initKernelSettingsExecuted()));

  queries.push_back(query);

  settings_db->execute(query);
}

void KernelPrivate::initDeliveryProfiles() {
  dbmanager::SqlQuery *query = new dbmanager::SqlQuery;

  query->add(QString("SELECT dp.`id`, dp.`name` "
                     "FROM `delivery_profiles` dp,"
                     "`source_sessions` ss,"
                     "`destination_sessions` ds "
                     "WHERE (ss.`kernel_id`=%1 OR ds.`kernel_id`=%1) AND "
                     "(ss.`delivery_profile_id`=dp.`id` OR "
                     "ds.`delivery_profile_id`=dp.`id`) GROUP BY dp.`id`")
             .arg(Kernel::instance()->getKernelId()));

  connect(query, SIGNAL(executed()),
          this, SLOT(initDeliveryProfilesExecuted()));

  queries.push_back(query);

  settings_db->execute(query);
}

void KernelPrivate::initRouteTable() {
  dbmanager::SqlQuery *query = new dbmanager::SqlQuery;

  query->add(QString("SELECT rt.`id`,"
                     "rt.`src_session_id`,"
                     "rt.`dest_session_id`,"
                     "rt.`src_addr`,"
                     "rt.`dest_addr`,"
                     "rt.`metric` "
                     "FROM `route_table` rt,"
                     "`destination_sessions` ds "
                     "WHERE rt.`dest_session_id`=ds.`id` AND "
                     "ds.`kernel_id`=%1")
             .arg(Kernel::instance()->getKernelId()));

  connect(query, SIGNAL(executed()),
          this, SLOT(initRouteTableExecuted()));

  queries.push_back(query);

  settings_db->execute(query);
}

void KernelPrivate::initPoolTranslator() {
  dbmanager::SqlQuery *query = new dbmanager::SqlQuery;

  query->add(QString("SELECT `id`,`begin_addr`,`end_addr` "
                     "FROM `pool_translator`"));

  connect(query, SIGNAL(executed()),
          this, SLOT(initPoolTranslatorExecuted()));

  queries.push_back(query);

  settings_db->execute(query);
}

void KernelPrivate::initSrcTranslator() {
  dbmanager::SqlQuery *query = new dbmanager::SqlQuery;

  query->add(QString("SELECT st.`id`,"
                     "st.`src_session_id`,"
                     "st.`dest_session_id`,"
                     "st.`src_addr`,"
                     "st.`dest_addr`,"
                     "st.`src_addr_after`,"
                     "st.`src_ton_after`,"
                     "st.`src_npi_after`,"
                     "st.`pool_id` "
                     "FROM `src_translator` st,`source_sessions` ss "
                     "WHERE st.`src_session_id`=ss.`id` AND ss.`kernel_id`=%1")
             .arg(Kernel::instance()->getKernelId()));

  connect(query, SIGNAL(executed()),
          this, SLOT(initSrcTranslatorExecuted()));

  queries.push_back(query);

  settings_db->execute(query);
}

void KernelPrivate::initSrcSessions() {
  dbmanager::SqlQuery *query = new dbmanager::SqlQuery;

  query->add(QString("SELECT `id`,"
                     "`name`,"
                     "`max_in_speed`,"
                     "`max_out_speed`,"
                     "`session_init_time`,"
                     "`enquire_link_time`,"
                     "`inactivity_time`,"
                     "`transaction_time`,"
                     "`max_in_trans`,"
                     "`max_out_trans`,"
                     "`bind_type`,"
                     "`system_id`,"
                     "`password`,"
                     "`billing_resource_id`,"
                     "`pay_only_delivered`,"
                     "`src_white_list`,"
                     "`src_black_list`,"
                     "`dest_white_list`,"
                     "`dest_black_list`,"
                     "`delivery_profile_id`,"
                     "`delivery_attempts` "
                     "FROM `source_sessions` "
                     "WHERE `kernel_id`=%1")
             .arg(Kernel::instance()->getKernelId()));

  connect(query, SIGNAL(executed()),
          this, SLOT(initSrcSessionsExecuted()));

  queries.push_back(query);

  settings_db->execute(query);
}

void KernelPrivate::initDestSessions() {
  dbmanager::SqlQuery *query = new dbmanager::SqlQuery;

  query->add(QString("SELECT `id`,"
                     "`name`,"
                     "`ip`,"
                     "`port`,"
                     "`max_in_speed`,"
                     "`max_out_speed`,"
                     "`session_init_time`,"
                     "`enquire_link_time`,"
                     "`inactivity_time`,"
                     "`transaction_time`,"
                     "`max_in_trans`,"
                     "`max_out_trans`,"
                     "`system_id`,"
                     "`password`,"
                     "`system_type`,"
                     "`error_code_rule`,"
                     "`message_id_rule`,"
                     "`status_rule`,"
                     "`deliver_message_id`,"
                     "`submit_message_id`,"
                     "`recovery_time`,"
                     "`billing_resource_id`,"
                     "`pay_only_delivered`,"
                     "`src_white_list`,"
                     "`src_black_list`,"
                     "`dest_white_list`,"
                     "`dest_black_list`,"
                     "`delivery_profile_id`,"
                     "`delivery_attempts` "
                     "FROM `destination_sessions` "
                     "WHERE `kernel_id`=%1")
             .arg(Kernel::instance()->getKernelId()));

  connect(query, SIGNAL(executed()),
          this, SLOT(initDestSessionsExecuted()));

  queries.push_back(query);

  settings_db->execute(query);
}

Kernel::Kernel() : QObject(0), p(new KernelPrivate) {
  kernel_instance = this;
}

Kernel::~Kernel() {
  if(p) {
    if(p->thread) {
      if(p->thread->isRunning()) {
        p->thread->quit();
        p->thread->wait();
      }
    }
    delete p;
    p = 0;
  }
}

Kernel *Kernel::instance() {
  return kernel_instance;
}

int Kernel::getSrcTimeoutErrorCode(quint32 src_session_id,
                                   quint32 error_code) {
  SrcSessionSettings *src_session_settings(
    getSrcSessionSettings(src_session_id));

  for(int i = 0; i < p->delivery_error_codes.size(); i++) {
    DeliveryErrorCodesRecord *pnt(p->delivery_error_codes[i]);
    if(pnt->delivery_profile_id == src_session_settings->delivery_profile_id &&
       pnt->error_code == error_code) {
      return pnt->timeout;
    }
  }

  return 60;
}

int Kernel::getDestTimeoutErrorCode(quint32 dest_session_id,
                                    quint32 error_code) {
  DestSessionSettings *dest_session_settings(
    getDestSessionSettings(dest_session_id));
  for(int i = 0; i < p->delivery_error_codes.size(); i++) {
    DeliveryErrorCodesRecord *pnt(p->delivery_error_codes[i]);
    if(pnt->delivery_profile_id == dest_session_settings->delivery_profile_id &&
       pnt->error_code == error_code) {
      return pnt->timeout;
    }
  }

  return 60;
}

quint32 Kernel::authSrcSession(const QString &system_id,
                               const QString &password,
                               quint32 *error_code) {
  QReadLocker locker(&p->lock);
  for(int i = 0; i < p->src_sessions_settings.size(); i++) {
    SrcSessionSettings *pnt(p->src_sessions_settings[i]);
    if(pnt->system_id == system_id) {
      if(pnt->password == password) {
        if(error_code) {
          *error_code = smpp::parameters::command_status::ESME_ROK;
        }
        return pnt->id;
      } else {
        if(error_code) {
          *error_code = smpp::parameters::command_status::ESME_RINVPASWD;
        }
        return 0;
      }
    }
  }
  if(error_code) {
    *error_code = smpp::parameters::command_status::ESME_RINVSYSID;
  }
  return 0;
}

KernelSettings *Kernel::settings() const {
  return p->settings;
}

void Kernel::altRoute(DestDeliverSm *message) {
  quint32 dest_session_id(0);

  QString src_addr(QString("%1:%2:%3")
                      .arg(message->dm.getDestinationAddr())
                      .arg(message->dm.getDestAddrTon())
                      .arg(message->dm.getDestAddrNpi()));

  QString dest_addr(QString("%1:%2:%3")
                      .arg(message->dm.getSourceAddr())
                      .arg(message->dm.getSourceAddrTon())
                      .arg(message->dm.getSourceAddrNpi()));

  int def_i(-1);

  quint16 min_metric(999);

  for(int i = 0; i < p->route_table.size(); i++) {
    RouteTableRecord *rec(p->route_table[i]);
    if(rec->src_addr.exactMatch(src_addr) &&
       rec->dest_addr.exactMatch(dest_addr) &&
       rec->src_session_id == message->src_session_id &&
       rec->dest_session_id == message->dest_session_id) {
      min_metric = rec->metric;
      def_i = i;
      break;
    }
  }

  if(def_i != -1) {
    int new_def_i(-1);

    quint16 new_min_metric(999);

    for(int i = 0; i < p->route_table.size(); i++) {
      RouteTableRecord *rec(p->route_table[i]);
      if(rec->src_addr.exactMatch(src_addr) &&
         rec->dest_addr.exactMatch(dest_addr) &&
         rec->src_session_id == message->src_session_id &&
         i != def_i &&
         rec->metric < new_min_metric &&
         rec->metric > min_metric) {
        new_min_metric = rec->metric;
        new_def_i = i;
      }
    }

    if(new_def_i != -1) {
      dest_session_id = p->route_table[new_def_i]->dest_session_id;
    }
  }

  message->alt_dest_session_id = dest_session_id;
}

QList<DestSessionSettings *> Kernel::getAllDestSessionSettings() const {
  return p->dest_sessions_settings;
}

SrcSessionSettings *Kernel::getSrcSessionSettings(quint32 id) const {
  for(int i = 0; i < p->src_sessions_settings.size(); i++) {
    SrcSessionSettings *pnt(p->src_sessions_settings[i]);
    if(pnt->id == id) {
      return pnt;
    }
  }

  return 0;
}

DestSessionSettings *Kernel::getDestSessionSettings(quint32 id) const {
  for(int i = 0; i < p->dest_sessions_settings.size(); i++) {
    DestSessionSettings *pnt(p->dest_sessions_settings[i]);
    if(pnt->id == id) {
      return pnt;
    }
  }

  return 0;
}

dbmanager::DatabaseManager *Kernel::dbSettings() {
  return p->settings_db;
}

dbmanager::DatabaseManager *Kernel::dbDirections() {
  return p->directions_db;
}

dbmanager::DatabaseManager *Kernel::dbMessages() {
  return p->messages_db;
}

quint32 Kernel::getKernelId() const {
  return p->settings->id;
}

quint32 Kernel::genMessageId() {
  if(p->settings->last_message_id == 0xFFFFFFFF) {
    p->settings->last_message_id = 0;
  } else {
    p->settings->last_message_id++;
  }

  if(p->settings->state != ADDED &&
     p->settings->state != DELETED) {
    p->settings->state = UPDATED;
  }

  return p->settings->last_message_id;
}

smslog::LogLevel Kernel::getLogLevel() const {
  return p->getLogLevel();
}

bool Kernel::readConfig(QSettings *config) {
  return p->readConfig(config);
}

void Kernel::start() {
  p->thread->start();
}

void Kernel::stop() {
  emit p->sigStop();
}

QString Kernel::getLastError() const {
  QReadLocker locker(&p->lock);
  return p->last_error;
}

} // namespace smsrouter
